"""
Unified Exception System for Device Connectors

Provides standardized error codes, exception classes, and error handling utilities.
"""

from __future__ import annotations

from datetime import datetime
from enum import Enum
from typing import Any


class ErrorCode(str, Enum):
    """Unified error codes for all connector types"""

    # Connection errors (1xxx) - Retryable
    CONNECTION_FAILED = "CONN_1001"
    CONNECTION_TIMEOUT = "CONN_1002"
    CONNECTION_REFUSED = "CONN_1003"
    AUTHENTICATION_FAILED = "CONN_1004"

    # Read errors (2xxx) - Retryable
    READ_FAILED = "READ_2001"
    READ_TIMEOUT = "READ_2002"
    INVALID_ADDRESS = "READ_2003"
    PERMISSION_DENIED = "READ_2004"

    # Write errors (3xxx) - Non-retryable
    WRITE_FAILED = "WRITE_3001"
    WRITE_TIMEOUT = "WRITE_3002"
    WRITE_PROTECTED = "WRITE_3003"

    # Device errors (4xxx)
    DEVICE_OFFLINE = "DEV_4001"
    DEVICE_ERROR = "DEV_4002"
    DEVICE_BUSY = "DEV_4003"

    # Protocol errors (5xxx) - Non-retryable
    PROTOCOL_ERROR = "PROTO_5001"
    INVALID_RESPONSE = "PROTO_5002"
    CRC_ERROR = "PROTO_5003"

    # System errors (9xxx)
    UNKNOWN_ERROR = "SYS_9001"
    INTERNAL_ERROR = "SYS_9002"


class BaseConnectorException(Exception):
    """
    Base exception for all connector errors

    Attributes:
        message: Human-readable error message
        error_code: Standardized error code
        retryable: Whether the operation can be retried
        details: Additional error context
        timestamp: When the error occurred
    """

    def __init__(
        self,
        message: str,
        error_code: ErrorCode = ErrorCode.UNKNOWN_ERROR,
        retryable: bool = False,
        details: dict[str, Any] | None = None,
    ):
        super().__init__(message)
        self.message = message
        self.error_code = error_code
        self.retryable = retryable
        self.details = details or {}
        self.timestamp = datetime.utcnow()

    def to_dict(self) -> dict[str, Any]:
        """Convert exception to dictionary for API responses"""
        return {
            "error_code": self.error_code.value,
            "message": self.message,
            "retryable": self.retryable,
            "details": self.details,
            "timestamp": self.timestamp.isoformat(),
        }

    def __str__(self) -> str:
        return f"[{self.error_code.value}] {self.message}"

    def __repr__(self) -> str:
        return (
            f"{self.__class__.__name__}("
            f"message='{self.message}', "
            f"error_code={self.error_code.value}, "
            f"retryable={self.retryable})"
        )


class ConnectionError(BaseConnectorException):
    """Connection-related errors (retryable)"""

    def __init__(
        self,
        message: str,
        error_code: ErrorCode = ErrorCode.CONNECTION_FAILED,
        details: dict[str, Any] | None = None,
    ):
        super().__init__(
            message=message, error_code=error_code, retryable=True, details=details
        )


class ReadError(BaseConnectorException):
    """Read operation errors (retryable)"""

    def __init__(
        self,
        message: str,
        error_code: ErrorCode = ErrorCode.READ_FAILED,
        details: dict[str, Any] | None = None,
    ):
        super().__init__(
            message=message, error_code=error_code, retryable=True, details=details
        )


class WriteError(BaseConnectorException):
    """Write operation errors (non-retryable)"""

    def __init__(
        self,
        message: str,
        error_code: ErrorCode = ErrorCode.WRITE_FAILED,
        details: dict[str, Any] | None = None,
    ):
        super().__init__(
            message=message, error_code=error_code, retryable=False, details=details
        )


class DeviceError(BaseConnectorException):
    """Device-specific errors"""

    def __init__(
        self,
        message: str,
        error_code: ErrorCode = ErrorCode.DEVICE_ERROR,
        retryable: bool = False,
        details: dict[str, Any] | None = None,
    ):
        super().__init__(
            message=message,
            error_code=error_code,
            retryable=retryable,
            details=details,
        )


class ProtocolError(BaseConnectorException):
    """Protocol-specific errors (non-retryable)"""

    def __init__(
        self,
        message: str,
        error_code: ErrorCode = ErrorCode.PROTOCOL_ERROR,
        details: dict[str, Any] | None = None,
    ):
        super().__init__(
            message=message, error_code=error_code, retryable=False, details=details
        )


class ConnectionTimeoutError(ConnectionError):
    """Connection timeout error (retryable)"""

    def __init__(
        self,
        message: str,
        details: dict[str, Any] | None = None,
    ):
        super().__init__(
            message=message,
            error_code=ErrorCode.CONNECTION_TIMEOUT,
            details=details,
        )


class DeviceOfflineError(DeviceError):
    """Device offline error (retryable)"""

    def __init__(
        self,
        message: str,
        details: dict[str, Any] | None = None,
    ):
        super().__init__(
            message=message,
            error_code=ErrorCode.DEVICE_OFFLINE,
            retryable=True,
            details=details,
        )
